import {
  CallHandler,
  ExecutionContext,
  ForbiddenException,
  Injectable,
  InternalServerErrorException, Logger, NestInterceptor,
} from '@nestjs/common'
import { AnalyticsService } from './analytics.service'
import { ModuleRef, Reflector } from '@nestjs/core'
import {
  ANALYTICS_ENABLED_KEY,
  ANALYTICS_OPTIONS_KEY,
  AnalyticsOptionsInternal,
} from './decorators/analyse.decorator'
import { getIp } from './get-ip.function'
import { ErrorCodes } from '@boticord/consts'
import { JwtService } from '@nestjs/jwt'
import { ConfigService } from '@nestjs/config'
import { FastifyRequest } from 'fastify'
import { extractCfAnalytics } from './extract-bc-analytics.function'
import { NotifyClientService } from '@boticord/notify-client'
import { Observable, tap } from 'rxjs'
import { notifiableEvents } from '@boticord/analytics/event-types.enum'
import { NotifyMessageDto } from '@boticord/notify-client/dto/notify-message.dto'
import { InjectRepository } from '@nestjs/typeorm'
import { Bot } from '@boticord/resources-client'
import { Repository } from 'typeorm'
import { UserWebhook } from '@boticord/users-client'

@Injectable()
export class AnalyticsInterceptor implements NestInterceptor {
  constructor(
    private readonly reflector: Reflector,
    private readonly moduleRef: ModuleRef,
    private readonly analytics: AnalyticsService,
    private readonly jwt: JwtService,
    private readonly config: ConfigService,
    private readonly notify: NotifyClientService,
    @InjectRepository(Bot) private readonly bots: Repository<Bot>,
    @InjectRepository(UserWebhook) private readonly webhooks: Repository<UserWebhook>,
  ) {}

  private readonly logger = new Logger(AnalyticsInterceptor.name)

  // interceptor but guard, because interceptors should return data. analytics should not return data
  async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
    const handlers = [ context.getHandler(), context.getClass() ]

    const isEnabled = this.reflector.getAllAndOverride<boolean>(ANALYTICS_ENABLED_KEY, handlers)
    if (!isEnabled) return next.handle()

    const options = this.reflector.getAllAndOverride<AnalyticsOptionsInternal>(ANALYTICS_OPTIONS_KEY, handlers)
    if (!options) return next.handle()

    const request = context.switchToHttp().getRequest<FastifyRequest>()

    const ip = getIp(request.headers)
    if (!ip) throw new InternalServerErrorException({ code: ErrorCodes.CannotDetectIp })
    this.logger.verbose(`ip: ${ip}`)

    const userAgent = request.headers['user-agent']
    if (!userAgent) throw new ForbiddenException({ code: ErrorCodes.AutomatedRequestsNotAllowed })
    this.logger.verbose(`user-agent: ${userAgent}`)

    const token = request.headers['authorization']
    const id = token
      ? this.jwt.verify(token,{ secret: this.config.getOrThrow('JWT_SECRET') })?.id
      : undefined

    return next.handle()
      .pipe(tap((response) => this.finish(context, response, request, options, ip, userAgent, id)))
  }

  private async finish(
    context: ExecutionContext,
    response: any,
    request: FastifyRequest,
    options: AnalyticsOptionsInternal,
    ip: string,
    userAgent: string,
    userID?: string,
  ) {
    const affected = await options.affected?.(context, response),
      user = userID ?? await options.user?.(context, response),
      extra = await options.extract?.(context, response)

    this.analytics.track(
      options.type, ip, userAgent, affected, user, extractCfAnalytics(
        request.headers, this.config.get('CF_ANALYTICS_SECRET'),
      ),
    )

    if (notifiableEvents.includes(options.type) && affected) {
      const affectd = await this.bots.findOne({ where: { id: affected } })
      if (affectd !== null) {
        const webhookSettings = await this.webhooks.findOne({ where: { id: affectd.owner } })

        const notify = new NotifyMessageDto()
        notify.type = options.type
        notify.id = affected
        notify.payload = extra
        notify.user = user
        notify.happened = Date.now()
        notify.webhookSettings = {
          url: webhookSettings?.url,
          headers: webhookSettings?.headers ?? {},
          enabled: webhookSettings?.enabled ?? false,
        }

        this.notify.broadcastMessage(notify)
          .catch(e => this.logger.error(e))
      }
    }

    return response
  }
}
